using System;
using System.Collections.Generic;
using System.Reflection;
using Framework.Common.Cache;
using PostSharp.Aspects;
using System.Linq;

namespace Framework.Common.AOP.Cache
{
    [Serializable]
    public class CacheTriggerAttribute : OnMethodBoundaryAspect
    {

        private readonly string methodFormatString;

        private readonly MethodInfo methodInfo;

        private int[] parameterIndexes;

        private static readonly MethodDict dict = new MethodDict();
        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="methodName"></param>
        public CacheTriggerAttribute(Type type, string methodName)
        {
            methodInfo = dict.GetMethod(type, methodName);
            methodFormatString = Formatter.GetMethodFormatString(methodInfo).ToString();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        public override void OnExit(MethodExecutionArgs args)
        {
            ICache cache = CacheFactory.CacheInstance;
            Object[] paramters = null;
            if (parameterIndexes != null && parameterIndexes.Length > 0)
            {
                paramters = new object[parameterIndexes.Length];
                foreach (var i in parameterIndexes)
                {
                    paramters[i] = args.Arguments[i];
                }
            }
            string cacheKey = Formatter.Format(null, paramters, methodFormatString);
            if (cache.Contains(cacheKey)) cache.Remove(cacheKey);
            base.OnExit(args);
        }
        /// <summary>
        /// CompileTimeInitialize
        /// </summary>
        /// <param name="method"></param>
        /// <param name="aspectInfo"></param>
        public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
        {
            var indexedParams = methodInfo.GetParameters().Select((p, index) => new { index, p.ParameterType, p.Name });
            parameterIndexes = (from origin in indexedParams
                                join current in method.GetParameters()
                                on new { origin.Name, origin.ParameterType } equals new { current.Name, current.ParameterType }
                                select origin.index).ToArray();
            base.CompileTimeInitialize(method, aspectInfo);
        }

        #region dict for method
        /// <summary>
        /// MethodDict
        /// </summary>
        class MethodDict
        {
            private readonly Dictionary<Type, Dictionary<string, MethodInfo>> dict;
            /// <summary>
            /// constructor
            /// </summary>
            public MethodDict()
            {
                dict = new Dictionary<Type, Dictionary<string, MethodInfo>>();
            }
            /// <summary>
            /// get from dict
            /// </summary>
            /// <param name="type"></param>
            /// <param name="methodName"></param>
            /// <returns></returns>
            public MethodInfo GetMethod(Type type, string methodName)
            {
                if (!dict.ContainsKey(type))
                {
                    dict.Add(type, new Dictionary<string, MethodInfo>());
                }
                Dictionary<string, MethodInfo> subDict = dict[type];
                if (!subDict.ContainsKey(methodName))
                {
                    MethodInfo mi = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static);
                    if (mi == null)
                    {
                        throw new ArgumentException("method cannot find : " + methodName);
                    }
                    subDict.Add(methodName, mi);
                }
                return subDict[methodName];
            }
        }
        #endregion
    }

}

